<template>



    <div id="view_cropper" ref="ViewCropperRef">
        <div class="cropper-header">
            header control bar
        </div>
        <div class="cropper-input" ref="CropperInputRef">
            <div class="input-item">
                <label class="form-label">文件名:</label>
                <span class="tw-text-center">
                    {{ compControl.controller.file_info.file_name }}.{{
                        compControl.controller.file_info.file_ext_name }}
                </span>
            </div>
            <div class="input-item">
                <label class="form-label">文件大小:</label>
                <span class="tw-text-center">
                    {{ compControl.controller.file_info.file_size_text }}
                </span>
            </div>
            <div class="input-item">
                <label class="form-label">图像大小:</label>
                <span class="tw-text-center">
                    {{ compControl.controller.file_info.image_size.width }} x
                    {{ compControl.controller.file_info.image_size.height }} 像素
                </span>
            </div>
            <div class="input-item">
                <label class="form-label">读取类型:</label>
                <span class="tw-text-center" v-if="compControl.controller.file_info.file_ext_name">
                    Base64/{{ compControl.controller.file_info.file_ext_name.toUpperCase() }}
                </span>
            </div>
            <hr style="margin: 0.5rem 0 0 0;">
            <div class="input-item">
                <label class="form-label" for="input-x">选框位置X</label>
                <input id="input-x" type="number" v-model="compControl.controller.property.x"
                    @input="compControl.controller.editSelection('x', compControl.controller.property.x)">
            </div>
            <div class="input-item">
                <label class="form-label" for="input-y">选框位置Y</label>
                <input id="input-y" type="number" v-model="compControl.controller.property.y"
                    @input="compControl.controller.editSelection('y', compControl.controller.property.y)">
            </div>
            <div class="input-item">
                <label class="form-label" for="input-w">选框宽度</label>
                <input id="input-w" type="number" v-model="compControl.controller.property.width">
            </div>
            <div class="input-item">
                <label class="form-label" for="input-h">选框高度</label>
                <input id="input-h" type="number" v-model="compControl.controller.property.height">
            </div>
            <div class="input-item">
                <label class="form-label" for="input-r">图像旋转</label>
                <input id="input-r" type="number" v-model="compControl.controller.property.rotate"
                    @input="compControl.controller.editSelection('rotate', compControl.controller.property.rotate)">
            </div>
            <div class="input-item">
                <label class="form-label" for="input-sx">图像X缩放</label>
                <input id="input-sx" type="number" v-model="compControl.controller.property.scaleX"
                    @input="compControl.controller.editSelection('scaleX', compControl.controller.property.scaleX)">
            </div>
            <div class="input-item">
                <label class="form-label" for="input-sy">图像Y缩放</label>
                <input id="input-sy" type="number" v-model="compControl.controller.property.scaleY"
                    @input="compControl.controller.editSelection('scaleY', compControl.controller.property.scaleY)">
            </div>
            <hr style="margin: 0.5rem 0 0 0;">
            <div class="input-item">
                <label class="form-label">裁剪比例</label>
                <input type="number" style="width: 2rem;" id="XYR-X" v-model="compControl.controller.other_limit.XYR.x"
                    @input="compControl.controller.handleEditRatio()">
                <span class="tw-mx-05">:</span>
                <input type="number" style="width: 2rem;" id="XYR-Y" v-model="compControl.controller.other_limit.XYR.y"
                    @input="compControl.controller.handleEditRatio()">
                <span class="tw-mx-1"></span>
                <button style="height: 21px;" @click="compControl.controller.other_limit.XYR.x = undefined;
                compControl.controller.other_limit.XYR.y = undefined;
                compControl.controller.handleEditRatio()">取消</button>
                <!--select name="rates" id="select-radio" style="height: 19px;width: 5rem;">
                    <option value="16.9">16:9</option>
                    <option value="16.10">16:10</option>
                    <option value="4.3">4:3</option>
                    <option value="3.2">3:2</option>
                    <option value="1.1">1:1</option>
                </!--select-->
            </div>


            <div class="input-item">
                <label class="form-label">裁剪形状</label>
                <input id="radio-rectangle" type="radio" name="shape" value="rectangle"
                    v-model="compControl.controller.other_limit.shape" @input="compControl.controller.handleEditRatio();
                    ">
                <label for="radio-rectangle">矩形</label>
                <input id="radio-circle" type="radio" name="shape" value="circle"
                    v-model="compControl.controller.other_limit.shape"
                    @input="compControl.controller.handleUseCircleShape()">
                <label for="radio-circle">圆形</label>
            </div>
            <div class="input-item">
                <label class="form-label" for="select-size">裁剪尺寸</label>
                <select name="sizes" id="select-size" style="height: 21px;width: 6rem;"
                    v-model="compControl.controller.other_limit.crop_inch"
                    @change="compControl.controller.handleEditSize($event.target.value)">
                    <!-- TODO选了这个之后不更新输出 -->
                    <option value="any">任意</option>
                    <option value="x1c">小一寸</option>
                    <option value="1c">一寸</option>
                    <option value="d1c">大一寸</option>
                    <option value="2c">两寸</option>
                    <option value="d2c">大两寸</option>
                </select>
                <button style="height: 21px;" @click="() => {
                    const ox = compControl.controller.property.width
                    compControl.controller.property.width = compControl.controller.property.height
                    compControl.controller.property.height = ox
                }">转置</button>
                <span style="color: gray;font-size: x-small;">(300dpi)</span>
            </div>
            <hr style="margin: 0.5rem 0 0.5rem 0;">
            <div class="tw-flex tw-flex-end" style="padding: 0 1rem;">
                <!-- 
                <button @click="compControl.control.handleReset()" class="tw-mr-1">重新上传</button> -->
                <input id="imgReader" ref="imageUloadRef" type="file" accept="image/*" style="display: none;"
                    :onchange="compControl.controller.handleLoadImage">
                <button class="tw-mr-2" @click="imageUloadRef.click()">选择文件</button>
                <button @click="compControl.controller.handleReset()">重置选区</button>
            </div>
        </div>
        <!-- -------------------------------------------------------------------- -->
        <div class="cropper-process-container">
            <div class="container-mask" ref="CropperContainerMaskRef" :class="compControl.controller.other_limit.shape + ' ' +
                compControl.controller.other_control.reference_line">
                <img ref="imageRef" class="fit img" src="/105822330_p0.jpg" />
            </div>
        </div>
        <!-- -------------------------------------------------------------------- -->
        <div class="cropper-output" ref="CropperOutputRef">

            <section style="display: flex;flex-wrap: wrap;flex-direction: row;">
                <div>
                    <img class="preview-image" :src="output_img_preview_src" alt="">
                </div>
                <div style="display: flex;flex-direction: row;flex-wrap: wrap;;">
                    <div>
                        <img class="preview-image s100" :src="output_img_preview_src" alt="">
                        <img class="preview-image s50" :src="output_img_preview_src" alt="">
                    </div>
                    <div style="display: flex; align-items: flex-start;" >
                        <img class="preview-image s100 circle" :src="output_img_preview_src" alt="">
                        <img class="preview-image s50 circle" :src="output_img_preview_src" alt="">
                        <img class="preview-image s25 circle" :src="output_img_preview_src" alt="">
                    </div>
                    
                </div>

            </section>
            <section>
                <div class="input-item">
                    <label class="form-label" for="output-name">输出文件名</label>
                    <input id="output-name" type="text" v-model="compControl.output.property.file_name">
                </div>
                <div class="input-item">
                    <label class="form-label">输出尺寸:</label>
                    <span class="tw-text-center">
                        {{ compControl.controller.property.width }} x
                        {{ compControl.controller.property.height }} 像素
                        <span v-if="compControl.controller.other_limit.shape == 'circle'">
                            (圆形)
                        </span>
                    </span>
                </div>
                <div class="input-item">
                    <label class="form-label">文件大小:</label>
                    <span class="tw-text-center">
                        {{ compControl.output.property.file_size_text }}
                    </span>
                </div>
                <hr style="margin: 0.5rem 0 0.5rem 0;">
                <div class="tw-flex tw-flex-start" style="padding: 0 1rem;">
                    <button @click="compControl.output.saveSelection()">保存下载</button>
                </div>
            </section>
        </div>
        <div class="cropper-setting">
            <section style="flex: 1;">

            </section>
            <section style="flex: 4;">
                <div>
                    <label for="crosshair-length">准星长度</label>
                    <input id="crosshair-length" type="number" style="width: 3rem; height: 19px;"
                        @input="compControl.controller.other_control.handleCrosshairInput()"
                        v-model="compControl.controller.other_control.crosshair_length" />
                    <label for="crosshair-length" style="position: relative;">
                        <span style="position: absolute;left: -1.125rem;top:1px;
                            color: gray;font-size: small;user-select: none;">px</span>
                    </label>
                </div>
                <div class="tw-ml-4">
                    <label for="crosshair-length">准星颜色</label>
                    <input type="color" v-model="compControl.controller.other_control.crosshair_color" @input=" ViewCropperRef.style.setProperty('--crosshair_color', compControl.controller.other_control.crosshair_color);
                    compControl.controller.other_control.crosshair_color = compControl.controller.other_control.crosshair_color.toLowerCase()
                        " :style="{ width: '3rem', height: '21px' }">
                    <!--label style="position: relative;">
                        <span :style="{
                            position: 'absolute', left: '-1.125rem', top: '1px',
                            width: '1rem', height: '1rem',
                            backgroundColor: compControl.controller.other_control.crosshair_color
                        }">
                        </span>
                    </label-->

                </div>
                <div class="tw-ml-4">
                    <label for="crosshair-length">参考线</label>
                    <select id="select-reference" style="height: 21px;width: 8.25rem;"
                        v-model="compControl.controller.other_control.reference_line">
                        <option value="tri_sector">三等分</option>
                        <option value="golden_selection">黄金分割</option>
                        <option value="opposite_angle">45°对角</option>
                        <option value="triangleLB-RT">对角三角形LB-RT</option>
                        <option value="triangleLT-RB">对角三角形LT-RB</option>
                    </select>
                </div>
            </section>
            <section style="flex: 1;">

            </section>


        </div>
    </div>
</template>

<script setup>
import { watch, onMounted, ref, reactive, computed } from "vue";
// 引入cropperjs库与相关css
import Cropper from "cropperjs";
import "cropperjs/dist/cropper.min.css";
const ViewCropperRef = ref(null)
//////////////////////Input
const CropperInputRef = ref(null)
const imageUloadRef = ref(null)
//////////////////////Process
var cropper = null;
const imageRef = ref(null) //img元素
const CropperContainerMaskRef = ref(null)
//////////////////////Output
const CropperOutputRef = ref(null)
const output_img_preview_src = ref('')
//////////////////////

const compControl = reactive({
    controller: {
        file_info: {
            file_name: '',
            file_ext_name: '',
            file_size: 0,
            file_size_text: "",
            image_size: { width: 0, height: 0 }

        },
        property: {
            x: 0, y: 0,
            width: 0, height: 0,
            rotate: 0,
            scaleX: 0, scaleY: 0,
        },
        other_limit: {
            XYR: { x: undefined, y: undefined },
            shape: "rectangle",
            crop_inch: "any",

        },
        other_control: {
            crosshair_length: 11,
            crosshair_color: '#ff0000',
            reference_line: "tri_sector",
            handleCrosshairInput: () => {
                if (compControl.controller.other_control.crosshair_length % 2 == 0) {
                    compControl.controller.other_control.crosshair_length += 1
                }
                ViewCropperRef.value.style.setProperty('--crosshair_length', compControl.controller.other_control.crosshair_length + 'px')
                //                const t = getComputedStyle(a).getPropertyValue('--crosshair_length').trim();

            }
        },
        /*getSelection: () => {
            const dom = cropper.getData()
            console.log(dom);
        },*/
        editSelection: (key = '', value = 0) => {
            //修改选区属性
            const nd = {}
            nd[key] = value
            cropper.setData(nd)
            ///////////////
            if (key == 'width' || key == 'height') {
                compControl.controller.other_limit.crop_inch = 'any'
            }
        },
        handleEditRatio(ratio = 0) {
            //修改裁减比例
            if (!ratio) {
                ratio = compControl.controller.other_limit.XYR.x / compControl.controller.other_limit.XYR.y
            }
            cropper.setAspectRatio(ratio)
            updateControlData()
            generateBase64Image(cropper.getCroppedCanvas());
        },
        handleUseCircleShape() {
            //修改裁剪形状（圆形/矩形）
            //TODO [bug]  compControl.controller.other_limit.shape迟缓切换属性
            const m = Math.min(compControl.controller.property.width, compControl.controller.property.height)
            compControl.controller.property.width = m;
            compControl.controller.property.height = m;
            compControl.controller.handleEditRatio(1)
        },
        handleEditSize(value = '1') {
            //修改裁剪尺寸
            switch (value) {
                case 'x1c':
                    compControl.controller.property.width = 259
                    compControl.controller.property.height = 377
                    break;
                case '1c':
                    compControl.controller.property.width = 295
                    compControl.controller.property.height = 413
                    break;
                case 'd1c':
                    compControl.controller.property.width = 389
                    compControl.controller.property.height = 566
                    break;
                case '2c':
                    compControl.controller.property.width = 413
                    compControl.controller.property.height = 579
                    break;
                case 'd2c':
                    compControl.controller.property.width = 423
                    compControl.controller.property.height = 626
                    break;
                default:
                    break;
            }
        },
        handleReset: () => {
            compControl.controller.other_limit.XYR.x = undefined;
            compControl.controller.other_limit.XYR.y = undefined;
            compControl.controller.handleEditRatio()
            cropper.reset()
        },
        handleLoadImage(event) {
            const f = event.target.files[0]
            if (f) {
                const names = f.name.split('.')
                compControl.controller.file_info.file_name = names.slice(0, names.length - 1).join('')
                compControl.controller.file_info.file_ext_name = names[names.length - 1]
                compControl.controller.file_info.file_size = f.size
                compControl.controller.file_info.file_size_text = file_size_computer(f.size)
                /////////////
                const date = new Date()
                const year = date.getFullYear();
                const month = (date.getMonth() + 1).toString().padStart(2, '0');
                const day = date.getDate().toString().padStart(2, '0');
                const date_string = `${year}-${month}-${day}`;
                compControl.output.property.file_name = compControl.controller.file_info.file_name + `[E${date_string}].png`
                //compControl.output.property.file_ext_name = compControl.controller.file_info.file_ext_name

                //readAsDataURL方法可以将File对象转化为data:URL格式的字符串（base64编码）
                const reader = new FileReader();
                reader.readAsDataURL(f);
                reader.onload = (e) => {
                    let dataURL = reader.result;
                    //将img的src改为刚上传的文件的转换格式
                    imageRef.value.src = dataURL
                    cropper.replace(dataURL)
                }
            }
        }, handleChangeReferenceLine() {

        }
    },
    output: {
        property: {
            file_name: "",
            file_ext_name: 'png',
            file_size: 0,
            file_size_text: "",
            image_size: "",
        },
        saveSelection: (base64 = output_img_preview_src.value) => {
            const byteCharacters = atob(base64.split(",")[1]);
            const byteNumbers = new Array(byteCharacters.length);
            for (let i = 0; i < byteCharacters.length; i++) {
                byteNumbers[i] = byteCharacters.charCodeAt(i);
            }
            const byteArray = new Uint8Array(byteNumbers);
            let blob = new Blob([byteArray], { type: "image/png" });
            let url = URL.createObjectURL(blob);
            if (compControl.controller.other_limit.shape == 'circle') {
                const img = new Image()
                img.src = url
                img.onload = () => {
                    const canvas = document.createElement("canvas")
                    const ctx = canvas.getContext('2d');
                    // 以最短边为直径
                    const size = Math.min(compControl.controller.property.width, compControl.controller.property.height);
                    canvas.width = size;
                    canvas.height = size;
                    // 绘制圆形
                    ctx.beginPath();
                    ctx.arc(size / 2, size / 2, size / 2, 0, 2 * Math.PI);
                    ctx.closePath()
                    ctx.clip();
                    // 绘制图片
                    ctx.drawImage(img, 0, 0, size, size);
                    // 导出为Blob
                    canvas.toBlob((newBlob) => {
                        URL.revokeObjectURL(url)
                        blob = newBlob
                        url = URL.createObjectURL(newBlob)
                        download_blob(url)
                        //TODO 小文件裁剪完 下载太大了
                        //TODO 大文件裁剪完 文件太小了，失真严重
                    }, 'image/png');
                }
            } else {
                download_blob(url)
            }

            function download_blob(url = '') {
                // 创建一个隐藏的 <a> 元素
                const link = document.createElement("a");
                link.href = url;
                link.download = compControl.output.property.file_name; // 设置下载文件名
                document.body.appendChild(link); // 将链接添加到页面
                link.click(); // 触发下载
                document.body.removeChild(link); // 下载完成后移除链接
                URL.revokeObjectURL(url); // 释放 URL 对象
            }


        }
    }
})
//////////////////////////////////////////////
//                                          //
//          M A I N    PROCESSIR            //
//                                          //
//////////////////////////////////////////////
function cropper_init() {
    cropper = new Cropper(imageRef.value, {
        //aspectRatio: 1,
        //aspectRatio: (compControl.controller.other_limit.XYR.x / compControl.controller.other_limit.XYR.y), //裁剪比例 → [1 / 1, 19 / 9 ,...........]
        viewMode: 1, //裁剪模式 [0,1,2,3]
        dragMode: "move",
        // 重点：加载完成将第一次裁剪的图片数据发给父级
        ready() {
            const img_data = cropper.getImageData()
            compControl.controller.file_info.image_size.width = img_data.naturalWidth
            compControl.controller.file_info.image_size.height = img_data.naturalHeight
            updateControlData()
            generateBase64Image(cropper.getCroppedCanvas());
        },
        // 鼠标、手指移开触发
        cropend() {
            updateControlData()
            generateBase64Image(cropper.getCroppedCanvas());
        },
        // 缩放触发，这里最好用防抖函数，不然缩放非常卡，影响用户体验，加入我已有防抖，例子如下
        zoom: debounce(function () {
            updateControlData()
            generateBase64Image(cropper.getCroppedCanvas());
        }, 300),
        /*zoom() {
            console.log(1);

            updateControlData()
            generateBase64Image(this.cropper.getCroppedCanvas());
        },*/
        /*// 缩放触发
        zoom() {
            clipImgEmitBase64Img(this.cropper.getCroppedCanvas());
        },*/
    });
}
function generateBase64Image(cvs) {
    // 将canvas转为图片数据 → 参数1:图片格式[image/webp, image/jpeg, image/png] | 参数2：图片裁剪后的清晰度，相当于压缩图片 0 - 1(只对jpeg与webp有效)
    const b64data = cvs.toDataURL("image/webp", 0.75);
    output_img_preview_src.value = b64data
    const strlength = b64data.length
    const fileSize = strlength - ((strlength / 8) * 2)//单位是Byte，需要/1024得到KiByte
    compControl.output.property.file_size = fileSize
    compControl.output.property.file_size_text = file_size_computer(fileSize)
    //console.log(file_size_computer(fileSize));

}

function updateControlData() {
    const R = cropper.getData()
    for (let key in R) {
        R[key] = parseInt(R[key])
    }
    compControl.controller.property = R
}

function file_size_computer(ByteNumber = 0) {
    let file_size_text = ''
    if (ByteNumber > 1024 * 1024) {
        file_size_text = (ByteNumber / 1024 / 1024).toFixed(1) + 'MiB'
    } else if (ByteNumber > 1024) {
        file_size_text = (ByteNumber / 1024).toFixed(1) + 'KiB'
    } else {
        file_size_text = ByteNumber.toFixed(1) + 'Byte'
    }
    return file_size_text
}

function debounce(fn, wait) {
    var timer = null;
    return function () {
        if (timer !== null) {
            clearTimeout(timer);
        }
        timer = setTimeout(fn, wait);
    }
}
//////////////////////////////////////////////////
//////////////////////////////////////////////////
//////////////////////////////////////////////////

watch(() => compControl.controller.property.width, (nv) => {
    //console.log(cropper.getCropBoxData().width);
    //TODO 这里能不能放进css里
    const t = document.getElementsByClassName("cropper-crop-box")[0]
    const w = parseFloat(getComputedStyle(t).getPropertyValue('width').trim())
    const h = parseFloat(getComputedStyle(t).getPropertyValue('height').trim())
    t.style.setProperty('--selector_width', w + 'px')
    t.style.setProperty('--selector_height', h + 'px')
    //////////计算对角线角度
    const op_line_deg = Math.atan(w / h) * (180 / Math.PI)
    t.style.setProperty('--selector_opposite_angle', op_line_deg.toFixed(8) + 'deg')
    //////////计算过对角线和顶点的高的线段长度和角度
    const op_line_h_length = (w * h) / (Math.sqrt(w * w + h * h))
    t.style.setProperty('--selector_opposite_line_length', op_line_h_length.toFixed(1) + 'px')
    const op_line_h_deg = Math.acos(op_line_h_length / h) * (180 / Math.PI)
    t.style.setProperty('--selector_opposite_line_deg', op_line_h_deg.toFixed(1) + 'deg')
    ///////////////////////////////////////
    compControl.controller.editSelection('width', nv)
})
watch(() => compControl.controller.property.height, (nv) => {

    compControl.controller.editSelection('height', nv)
})


onMounted(() => {
    cropper_init()
});




</script>

<style scoped lang="scss">
@use "@/assets/cropper-item.scss";

#view_cropper {

    $cop_height: calc(100vh - var(--framework-header-height) - 5rem); //TODO vh高度有bug的
    margin-top: 0.25rem;
    //height: 1000px;
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    height: $cop_height;

    .cropper-header {
        flex-basis: 100%;
        margin-bottom: 0.25rem;
        display: flex;
        flex-direction: row;
        flex-wrap: wrap;
        background-color: #00000008;
        min-height: 1.5rem;
    }

    .cropper-input {

        height: $cop_height;
        background-color: #00000008;
        border-radius: 0.375rem;
        min-width: 10rem;
        flex: 1;
        padding: 0.25rem;
    }

    .cropper-output {
        --image-size: 100px;

        height: $cop_height;
        background-color: #00000008;
        border-radius: 0.375rem;
        flex: 1;
        padding: 0.25rem;

        .preview-image {

            &.s100,
            &.s50,
            &.s30 {
                margin-right: 0.25rem;
            }

            //margin: 0.125rem;
            &.s100 {
                max-height: 100px;
            }

            &.s50 {
                max-height: 50px;
            }

            &.s25 {
                max-height: 25px;
            }

            &.circle {
                //overflow: hidden;
                border-radius: 100%;
            }
        }
    }


    .cropper-process-container {
        height: $cop_height;
        flex: 4;
        padding: 0.25rem;
        background-color: #20000050;
        border-radius: 0.375rem;
        overflow: hidden;

        .container-mask {
            height: calc($cop_height - 0.25rem); //TODO 不能只有这个绑定vh，两边的也要
            border: 1px solid #50000070;
        }
    }

    .cropper-setting {
        margin-top: 0.25rem;

        flex-basis: 100%;
        display: flex;
        flex-direction: row;
        flex-wrap: wrap;
        background-color: #00000008;

        section {
            display: flex;
            padding: 0.125rem 0.25rem;
        }
    }

}
</style>
<style scoped lang="scss">
img {
    max-width: 100%;
}

.input-item {
    display: inline-flex;
    width: 100%;
    align-items: center;
    //padding: 0 0.5rem;
    box-sizing: border-box;

    &:not(:first-child) {
        margin-top: 0.5rem;
    }

    .form-label {
        display: inline-block;
        width: 5.5rem;

        text-align: right;
        margin-right: 0.25rem;
    }

    input[type="number"],
    input[type="text"] {
        flex: 1;
    }


}

input[type="number"] {

    &::-webkit-inner-spin-button,
    &::-webkit-outer-spin-button {
        appearance: none;
    }
}
</style>